using System;
using System.Net;
using System.Net.Sockets;
using System.Collections;
using System.Text;
using System.Timers;
using System.Threading;
using System.IO;

namespace LANamp
{
    public class LanAmpServer
	{
        public delegate void Transmission();
        public delegate void SongUpdateEvent(string SongInfo);

		public event ClientList.UpdateEvent ClientsUpdated;
        public event Transmission Rx;
        public event Transmission Tx;
        public event SongUpdateEvent SongUpdate;

        private bool doSkip = false;
        private int winampBusy = 1;
		
		public LanAmpServer(System.Windows.Forms.Control Owner, string Servername, string AppVersion)
		{
            owner = Owner;
			localIP = new IPEndPoint(IPAddress.Any, 8642);
			clients = new ClientList(5);
            playlist = new PlayList();
            playlist.NewEntries += new PlayList.NewEntriesEvent(playlist_NewEntries);
			clients.Updated += new ClientList.UpdateEvent(clients_Updated);
			receiver = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp);
            netlistener = new TcpListener(localIP);
			try 
            {
                receiver.Bind(localIP);
                netlistener.Start();
            }
            catch (System.Exception e) 
            {
                System.Console.Out.WriteLine("ERROR: Can't bind to local socket; original error follows:\n"+e.ToString());
            }
			sendIP = new IPEndPoint(IPAddress.Broadcast,8642);
            InitWinamp();
            self = new Server(Client.ToIP(localIP), Servername, System.Environment.MachineName, AppVersion, winamp.Version,99);
			enc = Encoding.Unicode;
			sender = new UdpClient();
			aliveTimer = new System.Timers.Timer(3000);
			aliveTimer.Elapsed += new ElapsedEventHandler(TimeOut);
			aliveTimer.Start();
            winampTimer = new System.Timers.Timer(1000);
            winampTimer.Elapsed += new ElapsedEventHandler(winampTimer_Elapsed);
            winampTimer.Start();
            if (receiver.IsBound)
            {
                udplistener = new Thread(new ThreadStart(ReceiveLoop));
                udplistener.Name = "UdpListener";
                udplistener.IsBackground = true;
                udplistener.Start();
                tcplistener = new Thread(new ThreadStart(TcpReceiveConnection));
                tcplistener.Name = "TcpListener";
                tcplistener.IsBackground = true;
                tcplistener.Start();
            }
            SendMessage("LANamp-SERVERIAM");
		}

        void winampTimer_Elapsed(object sender, ElapsedEventArgs e)
        {
            if (winamp.WinAmpProcess != null)
            {
                if (doSkip)
                {
                    doSkip = false;
                    winamp.FadeOut();
                }
                else
                {
                    if (clients.Count > 0)
                    {
                        if (((float)clients.SkipVoteCount / (float)clients.Count) > 0.5)
                        {
                            doSkip = true;
                        }
                    }
                }
                if (playlist.Items.Count > 0)
                {
                    if (winamp.IsNotPlayingNorPaused && (winampBusy == 0))
                    {
                        winampBusy = 5;
                        ClearSkipVotes();
                        winamp.ClearPlaylist();
                        MusicInfo nextFile = new MusicInfo();
                        if (clients.NextClient != null)
                        {
                            Client firstClient = clients.NextClient;
                            Client newClient = clients.NextClient;
                            do
                            {
                                Console.WriteLine("Now to choose: " + firstClient.Nickname);
                                nextFile = GetNextSong(clients.CycleClient());
                                newClient = clients.NextClient;
                                Console.WriteLine("Next to choose: " + newClient.Nickname);
                                if (newClient == firstClient) break;
                            } while ((nextFile == null) || (nextFile.Filepath == ""));
                            Console.WriteLine("Really next to choose: " + newClient.Nickname);
                        }

                        while ((nextFile == null) || (nextFile.Filepath == null) || (nextFile.Filepath == ""))
                        {
                            nextFile = playlist.GetRandom();
                        }

                        if (nextFile != null)
                        {
                            Console.Out.WriteLine("Play: " + nextFile.DisplayName);
                            winamp.AppendToPlayList(nextFile.Filepath);
                            winamp.Play();
                        }
                    }
                    else
                    {
                        if (winampBusy > 0) winampBusy--;
                    }
                }
                string Currentsong = winamp.CurrentSong.Replace(" - ", "\n") + "\n(" + winamp.TrackPosition + " / " + winamp.TrackLength + ")";
                SendUpdate(winamp.CurrentSong, winamp.TrackPosition, winamp.TrackLength, clients.SkipVoteCount);
                DoSongUpdate(Currentsong);
            }
        }

        void winamp_Exited()
        {
            InitWinamp();
        }

        void InitWinamp()
        {
            winamp = new Winamp();
            winamp.Start();
            winamp.Exited += new Winamp.StateChangeEvent(winamp_Exited);
            winamp.Started += new Winamp.StateChangeEvent(winamp_Started);
        }

        void winamp_Started()
        {
            winamp.Initialize();
        }

		~LanAmpServer()
		{
			ClientsUpdated = null;
		}

		public void Kill()
		{
            winamp.CleanUp();
            aliveTimer.Stop();
	        TellGone();
		}

		public ClientList Clients
		{
			get
			{
				return clients;
			}
		}

        public PlayList Playlist
        {
            get
            {
                return playlist;
            }
        }

		private void TimeOut(object source, ElapsedEventArgs e) 
		{
			clients.TimeOut();
			TellAlive();
		}

		private void TellAlive()
		{
			SendMessage("LANamp-SERVERONLINE");
		}

		private void TellGone()
		{
			SendMessage("LANamp-SERVEROFFLINE");
		}

        public void ClearSkipVotes()
        {
            StreamWriter writer;
            foreach (Client client in clients.Items)
            {
                if ((client.ServerSocket != null) && (client.ServerSocket.Connected))
                {
                    writer = new StreamWriter(new NetworkStream(client.ServerSocket));
                    writer.WriteLine("LANamp-CLEARSKIPVOTE");
                    writer.Flush();
                }
            }
            clients.ClearSkipVotes();
            writer = null;
        }

        public void TellAboutSkipVote(string whoSkipvoted)
        {
            StreamWriter writer;
            foreach (Client client in clients.Items)
            {
                if ((client.ServerSocket != null) && (client.ServerSocket.Connected))
                {
                    writer = new StreamWriter(new NetworkStream(client.ServerSocket));
                    writer.WriteLine("LANamp-SKIPVOTED");
                    writer.WriteLine(whoSkipvoted);
                    writer.Flush();
                }
            }
            writer = null;
        }

        private MusicInfo GetNextSong(Client client)
        {
            if (client == null) return null;
            StreamWriter writer = new StreamWriter(new NetworkStream(client.ServerSocket));
            StreamReader reader = new StreamReader(new NetworkStream(client.ServerSocket));
            writer.WriteLine("LANamp-GETNEXT");
            writer.Flush();
            Thread.Sleep(50);
            MusicInfo result = null;
            if (reader.ReadLine() == "LANamp-SETNEXT")
                result = new MusicInfo(reader);
            reader = null;
            writer = null;
            return result;
        }

        public void SendPlayList(PlayList newPart)
        {
            foreach (Client client in clients.Items)
            {
                SendPlayList(client, newPart);
            }
        }

        public void SendPlayList(Client client, PlayList newPart)
        {
            PlayList temp = newPart.Clone();
            StreamWriter writer;
            writer = new StreamWriter(new NetworkStream(client.ServerSocket));
            foreach (MusicInfo song in temp.Items)
            {
                SendSignal();
                writer.WriteLine("LANamp-PLAYLISTITEM");
                song.WriteToStream(writer);
            }
            writer.Flush();
            writer.WriteLine("LANamp-PLAYLISTSORT");
            writer.Flush();
            writer = null;
            temp = null;
        }

        public void SendMessage(String message)
		{
			Byte[] rawData;
			String data;
			String[] parts = new String[5];
			parts[0] = message;
			parts[1] = self.Name;
            parts[2] = self.Hostname;
            parts[3] = self.Version;
            parts[4] = self.WinampVersion;
			data = String.Join("\0", parts);
			rawData = enc.GetBytes(data);
            SendSignal();
            try
            {
                sender.Send(rawData, rawData.Length, sendIP);
            }
            catch
            {
                Console.WriteLine("No network available to send broadcasts over");
            }
		}

        public void SendUpdate(string TrackName, string TrackPosition, string TrackLength, int SkipVote)
        {
            Byte[] rawData;
            String data;
            String[] parts = new String[7];
            parts[0] = "LANamp-SERVERUPDATE";
            parts[1] = self.Name;
            parts[2] = TrackName;
            parts[3] = TrackPosition;
            parts[4] = TrackLength;
            parts[5] = clients.NextClientInfo;
            parts[6] = SkipVote.ToString();
            data = String.Join("\0", parts);
            rawData = enc.GetBytes(data);
            SendSignal();
            try
            {
                sender.Send(rawData, rawData.Length, sendIP);
            }
            catch
            {
                Console.WriteLine("No network available to send broadcasts over");
            }
        }

        private void SendSignal()
        {
        if (Tx != null)
            {
                if (owner.InvokeRequired)
                    owner.BeginInvoke(new Transmission(Tx));
                else
                    Tx();
            }
        }

        private void ReceiveSignal()
        {
            if (Rx != null)
            {
                if (owner.InvokeRequired)
                    owner.BeginInvoke(new Transmission(Rx));
                else
                    Rx();
            }
        }

        private void TcpReceiveConnection()
        {
            while (tcplistener.IsAlive)
            {
                Socket newSocket = netlistener.AcceptSocket();
                try
                {
                    if (newSocket.Connected) {
                        EndPoint remoteIP = newSocket.RemoteEndPoint;
                        byte[] rawData = new Byte[254];
                        ReceiveSignal();
                        int length = newSocket.Receive(rawData);
                        string data = enc.GetString(rawData).TrimEnd('\0');
                        string[] command = data.Split(new char[1] { '\0' });
                        if (command[0] == "LANamp-CONNECT")
                        {
                            if (command.Length < 2) break;
                            string server = command[1].Trim();
                            string appversion = (command.Length > 4) ? command[4].Trim() : "(unknown)";
                            string hostname = (command.Length > 3) ? command[3].Trim() : "";
                            string nickname = (command.Length > 2) ? command[2].Trim() : "(anonymous)";
                            Console.Out.WriteLine("TCP Connect: " + nickname + "@" + hostname + " (" + remoteIP.ToString() + ") v" + appversion);
                            clients.Alive(remoteIP, server, nickname, hostname, appversion);
                            Client connectedClient = clients.NewConnection(newSocket);
                            if (connectedClient == null)
                            {
                                Console.Out.WriteLine("ERROR: " + Client.ToIP(newSocket.RemoteEndPoint));
                                if (newSocket.Connected)
                                    newSocket.Disconnect(false);
                                newSocket = null;
                            }
                            else
                            {
                                SendPlayList(connectedClient, this.Playlist);
                            }
                        }
                        else
                        {
                            Console.Out.WriteLine("No LanAmp client: " + Client.ToIP(newSocket.RemoteEndPoint));
                            if (newSocket != null) 
                                if (newSocket.Connected)
                                    newSocket.Disconnect(false);
                            newSocket = null;
                        }
                    }
                    Thread.Sleep(5);
                }
                catch
                {
                }
                Thread.Sleep(50);
            }
        }

        public void ReceiveLoop() 
		{
			try 
			{
				Byte[] rawData;
				String data;
				EndPoint remoteIP = new IPEndPoint(IPAddress.Any, 0); //dummy endpoint
				while (udplistener.IsAlive) 
				{
                    if (receiver.Available > 0)
                    {
                        rawData = new Byte[254];
                        int length = receiver.ReceiveFrom(rawData, ref remoteIP);
                        ReceiveSignal();
                        data = enc.GetString(rawData).TrimEnd('\0');
                        string[] command = data.Split(new char[1] { '\0' }, 5);
                        switch (command[0])
                        {
                            case "LANamp-SERVERIAM":
                                {
                                    if (command.Length < 3) break;
                                    string hostname = command[2].Trim();
                                    string name = command[1].Trim();
                                    if (hostname == self.Hostname)
                                    {
                                        Console.Out.WriteLine("MyAlive: " + hostname + " (" + remoteIP.ToString() + ")");
                                        self.IPaddress = remoteIP.ToString().Split(':')[0];
                                    }
                                    break;
                                }
                            case "LANamp-ANNOUNCE":
                                {
                                    if (command.Length < 2) break;
                                    string server = command[1].Trim();
                                    if (server == self.IPaddress)
                                    {
                                        string appversion = (command.Length > 4) ? command[4].Trim() : "(unknown)";
                                        string hostname = (command.Length > 3) ? command[3].Trim() : "";
                                        string nickname = (command.Length > 2) ? command[2].Trim() : "(anonymous)";
                                        Console.Out.WriteLine("Alive: " + nickname + "@" + hostname + " (" + remoteIP.ToString() + ") v" + appversion);
                                        clients.Alive(remoteIP, server, nickname, hostname, appversion);
                                    }
                                    break;
                                }
                            case "LANamp-SKIPVOTE":
                                {
                                    Client client = clients.GetClient(remoteIP);
                                    Console.Out.WriteLine("SkipVote: " + client.Nickname + "@" + client.Host);
                                    client.DoSkipVote();
                                    TellAboutSkipVote(client.Nickname);
                                    break;
                                }
                            case "LANamp-BYEBYE":
                                {
                                    Client client = clients.GetClient(remoteIP);
                                    if (client != null)
                                    {
                                        Console.Out.WriteLine("Gone: " + client.Nickname + "@" + client.Host);
                                    }
                                    clients.Gone(remoteIP);
                                    break;
                                }
                        }
                        Thread.Sleep(5);
                    }
                    else
                    {
                        Thread.Sleep(50);
                    }
				}
			}
			catch (ThreadAbortException) 
			{
			}
		}
		public String Name
		{
			get
			{
				return self.Name;
			}
			set
			{
				self.Name = value;
                TellAlive();
			}
		}

        public string Version
        {
            get
            {
                return self.Version;
            }
        }

        public string WinampVersion
        {
            get
            {
                return self.WinampVersion;
            }
            set
            {
                self.WinampVersion = value;
            }
        }

        private Server self;
        private System.Windows.Forms.Control owner;
		private IPEndPoint localIP;
		private IPEndPoint sendIP;
		private ClientList clients;
		private Encoding enc;
		private Socket receiver;
		private UdpClient sender;
        private TcpListener netlistener;
		private System.Timers.Timer aliveTimer;
        private System.Timers.Timer winampTimer;
		private Thread udplistener;
        private Thread tcplistener;
        private Winamp winamp;
        private PlayList playlist;

        private void clients_Updated(String Server)
		{
            if (ClientsUpdated != null)
            {
                if (owner.InvokeRequired)
                {
                    object[] myArgs = new object[1];
                    myArgs[0] = Server.Clone();
                    owner.BeginInvoke(new ClientList.UpdateEvent(clients_Updated), myArgs);
                }
                else
                {
                    ClientsUpdated(Server);
                }
            }
		}

        void playlist_NewEntries(PlayList addplaylist)
        {
            SendPlayList(addplaylist);
        }

        private void DoSongUpdate(string SongInfo)
        {
            if (SongUpdate != null)
            {
                if (owner.InvokeRequired)
                {
                    object[] myArgs = new object[1];
                    myArgs[0] = SongInfo.Clone();
                    owner.BeginInvoke(new SongUpdateEvent(DoSongUpdate), myArgs);
                }
                else
                {
                    SongUpdate(SongInfo);
                }
            }
        }
    }

}
